# similarity_calculator.py
# -*- coding: utf-8 -*-

import difflib
import logging
from typing import List, Tuple, Dict, Any, Optional
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

# 导入项目模块
import config

# 配置日志记录器
logger = logging.getLogger(__name__)

# 定义字面匹配结果的数据结构
# 元组包含: (输入文本起始位置, 语料库文本起始位置, 匹配长度, 语料库文档 ID)
MatchTuple = Tuple[int, int, int, str]

def find_matches_sequencematcher(input_text: str, corpus_texts: Dict[str, str]) -> List[MatchTuple]:
    """
    使用 difflib.SequenceMatcher 在输入文本和多个语料库文本之间查找精确的字面匹配块。
    会根据 `config.MIN_MATCH_LENGTH` 配置项过滤掉长度过短的匹配块。
    """
    all_matches: List[MatchTuple] = []

    if not input_text:
        logger.warning("输入文本为空，无法使用 SequenceMatcher 进行比较。")
        return []
    if not corpus_texts:
        logger.warning("语料库文本字典为空，无法使用 SequenceMatcher 进行比较。")
        return []

    # 从配置获取最小匹配长度
    min_len_threshold = config.MIN_MATCH_LENGTH
    logger.info(f"开始使用 SequenceMatcher 进行字面匹配比较 (最小匹配长度: {min_len_threshold})...")
    valid_match_count = 0
    filtered_match_count = 0
    comparison_count = 0

    # 遍历每个语料库文档进行比较
    for corpus_id, corpus_text in corpus_texts.items():
        if not corpus_text:
            logger.debug(f"跳过空的语料库文档: {corpus_id}")
            continue

        comparison_count += 1
        logger.debug(f"正在将输入文本与语料库文档 '{corpus_id}' (长度: {len(corpus_text)}) 进行比较...")

        try:
            # 初始化 SequenceMatcher
            # isjunk=None: 不忽略任何字符
            # autojunk=False: 禁用自动将流行行视为“垃圾”的启发式方法，以查找更长的匹配
            matcher = difflib.SequenceMatcher(None, input_text, corpus_text, autojunk=False)

            # 获取所有匹配块: (i, j, n) 分别代表 input_text起始索引, corpus_text起始索引, 匹配块长度
            # 注意：get_matching_blocks() 返回的最后一个块总是一个虚拟的、长度为 0 的块，表示序列末尾
            matching_blocks = matcher.get_matching_blocks()

            # 遍历匹配块，排除最后一个虚拟块
            for i, j, n in matching_blocks[:-1]:
                # --- 应用最小长度过滤 ---
                if n >= min_len_threshold:
                    # --- 增加内容校验 ---
                    # 理论上 SequenceMatcher 应该返回精确匹配，但可以加一层保险
                    # if input_text[i : i + n] == corpus_text[j : j + n]:
                    match_info: MatchTuple = (i, j, n, corpus_id)
                    all_matches.append(match_info)
                    valid_match_count += 1
                    logger.debug(f"  找到有效匹配: 输入位置({i}-{i+n}), 来源({corpus_id}) 位置({j}-{j+n}), 长度={n}")
                    # else:
                    #     logger.warning(f"SequenceMatcher 报告匹配但内容不符: 输入='{input_text[i:i+n]}', 来源({corpus_id})='{corpus_text[j:j+n]}', 长度={n}")
                    #     filtered_match_count += 1
                elif n > 0: # 记录被过滤的、长度大于0但小于阈值的短匹配
                     filtered_match_count += 1
                     # 只记录日志，不添加到结果列表
                     logger.debug(f"  过滤短匹配: 长度={n} (< {min_len_threshold}), "
                                  f"输入='{repr(input_text[i:i+n])}', 来源='{corpus_id}' 位置({j}-{j+n})")

        except Exception as e:
            # 处理在比较特定文档时可能发生的错误
            logger.error(f"处理语料库文档 '{corpus_id}' 与 SequenceMatcher 比较时发生错误: {e}", exc_info=True)
            continue # 继续处理下一个语料库文档

    logger.info(f"SequenceMatcher 比较完成。与 {comparison_count} 个语料库文档进行了比较。")
    logger.info(f"共找到 {valid_match_count} 个长度 >= {min_len_threshold} 的字面匹配块。")
    logger.info(f"过滤掉了 {filtered_match_count} 个长度小于 {min_len_threshold} 的非空匹配块。")

    return all_matches


def calculate_ngram_cosine_similarity(
    input_text: str,
    corpus_texts: Dict[str, str],
    ngram_range: Tuple[int, int] = config.NGRAM_RANGE, # 从配置读取
    analyzer: str = config.NGRAM_ANALYZER           # 从配置读取
    ) -> Dict[str, float]:
    """
    计算输入文本与每个语料库文本之间的 N-gram TF-IDF 余弦相似度。

    这个相似度主要反映了文本间共享的字符序列（N-grams）的加权频率，
    可以作为文档主题相关性或风格相似性的一个辅助指标。
    """
    similarity_scores: Dict[str, float] = {}
    if not input_text:
        logger.warning("输入文本为空，无法计算 N-gram 相似度。")
        return similarity_scores
    if not corpus_texts:
        logger.warning("语料库文本字典为空，无法计算 N-gram 相似度。")
        return similarity_scores

    logger.info(f"开始计算 N-gram TF-IDF 余弦相似度 (分析器: {analyzer}, N-gram范围: {ngram_range})...")

    # 准备文本列表：第一个是输入文本，后面是所有语料库文本
    corpus_ids = list(corpus_texts.keys())
    all_texts = [input_text] + [corpus_texts[cid] for cid in corpus_ids] # 按顺序获取文本

    try:
        # 初始化 TF-IDF 向量化器
        # min_df=1 表示即使一个 N-gram 只出现一次也考虑在内
        vectorizer = TfidfVectorizer(
            analyzer=analyzer, ngram_range=ngram_range, min_df=1)

        # 拟合并转换所有文本
        tfidf_matrix = vectorizer.fit_transform(all_texts)

        # 计算输入文本（第一个向量）与所有语料库文本（从第二个向量开始）的余弦相似度
        # tfidf_matrix[0:1] 是输入文本的 TF-IDF 向量 (形状 1 x num_features)
        # tfidf_matrix[1:] 是所有语料库文本的 TF-IDF 向量 (形状 num_corpus x num_features)
        # cosine_similarity 返回一个矩阵，我们需要第一行
        cosine_similarities = cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:])

        # 检查返回结果的形状是否符合预期
        if cosine_similarities.shape == (1, len(corpus_ids)):
            # 将结果与语料库 ID 关联起来
            similarity_scores = {corpus_ids[i]: float(score) for i, score in enumerate(cosine_similarities[0])}
            # 记录一些得分较高的项，用于调试或验证
            top_scores = sorted(similarity_scores.items(), key=lambda item: item[1], reverse=True)[:5]
            logger.debug(f"N-gram 最高得分 (Top 5): {top_scores}")
        else:
             logger.error(f"计算得到的余弦相似度矩阵形状不符合预期: {cosine_similarities.shape}，预期为 (1, {len(corpus_ids)})")
             return {} # 返回空字典表示计算失败

    except ValueError as e:
        # 捕捉 TfidfVectorizer 可能产生的错误，例如文本为空或全是停用词（对于 N-gram 不太可能）
        logger.warning(f"计算 N-gram TF-IDF 时发生 ValueError: {e}。可能是输入文本/语料库处理后为空。")
        return {} # 返回空字典
    except Exception as e:
        # 捕获其他未知错误
        logger.exception(f"计算 N-gram 余弦相似度时发生未知错误: {e}")
        return {} # 返回空字典

    logger.info(f"N-gram 余弦相似度计算完成。共计算了 {len(similarity_scores)} 个语料库文档的得分。")
    return similarity_scores


# --- 主相似度计算函数 ---
def calculate_similarity(input_text: str, corpus_texts: Dict[str, str]) -> Dict[str, Any]:
    """
    计算输入文本与语料库文本之间的综合相似度信息。

    目前包括：
    1. 基于 `difflib.SequenceMatcher` 的字面匹配块。
    2. 基于 N-gram TF-IDF 的余弦相似度得分。
    """
    logger.info("开始计算综合相似度信息...")
    results: Dict[str, Any] = {
        'matches': [],
        'ngram_scores': {}
    }

    # 1. 执行 SequenceMatcher 字面匹配
    logger.debug("调用 find_matches_sequencematcher 进行字面匹配...")
    results['matches'] = find_matches_sequencematcher(input_text, corpus_texts)

    # 2. 计算 N-gram 余弦相似度 (如果启用)
    if config.USE_NGRAM_SIMILARITY:
        logger.debug("调用 calculate_ngram_cosine_similarity 计算 N-gram 相似度...")
        results['ngram_scores'] = calculate_ngram_cosine_similarity(input_text, corpus_texts)
    else:
        logger.info("配置中禁用了 N-gram 相似度计算，跳过此步骤。")

    logger.info("综合相似度信息计算完成。")
    return results
